package com.liyunc.socket.rpc.nio.centre;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ServerCentre implements Server {

    private final ExecutorService executorService =
        Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    private static final HashMap<String, Class> serviceRegister = new HashMap<>();

    private static boolean isRunning = false;

    private static int port;
    private static InetSocketAddress address;

    public ServerCentre(int port) {
        ServerCentre.port = port;
        ServerCentre.address = new InetSocketAddress("localhost", port);
    }

    public ServerCentre(InetSocketAddress address) {
        ServerCentre.address = address;
    }

    @Override
    public void stop() {
        isRunning = false;
        executorService.shutdown();
    }

    @Override
    public void start() throws IOException {
        isRunning = true;
        // 服务端进行channel创建
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(address);

        // 服务端进行selector创建，用于监听请求
        Selector serverSelector = Selector.open();
        serverSocketChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
        System.err.println("[RPC远程调用][SERVER START]");

        while (true) {
            // 通过selector的select()方法可以选择已经准备就绪的通道（包含注册）
            serverSelector.select();
            // 获取访问就绪的通道
            Iterator<SelectionKey> selectionKeyIterator = serverSelector.selectedKeys().iterator();
            while (selectionKeyIterator.hasNext()) {
                SelectionKey key = selectionKeyIterator.next();
                selectionKeyIterator.remove();
                // 若链接成功则进行数据接收
                if (key.isAcceptable()) {
                    // 创建新的连接，并且把连接注册到selector上，而且声明这个channel只对读操作感兴趣
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    if (socketChannel != null) {
                        socketChannel.configureBlocking(false);
                        socketChannel.register(serverSelector, SelectionKey.OP_READ);
                    }
                } else if (key.isReadable()) {
                    // 获取到client的写数据的channel
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    new StaticServiceTask(socketChannel, key).start();
                }
            }
        }
    }

    @Override
    public void register(Class serviceInterface, Class impl) {
        serviceRegister.put(serviceInterface.getName(), impl);
    }

    @Override
    public boolean isRunning() {
        return isRunning;
    }

    @Override
    public int getPort() {
        return port;
    }

    public static class StaticServiceTask {

        private final SocketChannel socketChannel;
        private final SelectionKey key;

        public StaticServiceTask(SocketChannel socketChannel, SelectionKey key) {
            this.socketChannel = socketChannel;
            this.key = key;
        }

        public void start() {
            // 读取到客户端传过来的信息，进行方法等解析
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            try {
                socketChannel.read(byteBuffer);
                ByteArrayInputStream byteInput = new ByteArrayInputStream(byteBuffer.array());
                ObjectInputStream input = new ObjectInputStream(byteInput);
                // 获取指定方法
                String serviceName = input.readUTF();
                String methodName = input.readUTF();
                Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
                Object[] arguments = (Object[]) input.readObject();
                Class serviceClass = serviceRegister.get(serviceName);
                Method method = serviceClass.getMethod(methodName, parameterTypes);
                Object result = method.invoke(serviceClass.newInstance(), arguments);
                key.interestOps(SelectionKey.OP_WRITE);
                // 返回执行结果
                ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
                ObjectOutputStream output = new ObjectOutputStream(byteOutput);
                output.writeObject(result);
                output.flush();
                byte[] bytes = byteOutput.toByteArray();
                byteBuffer.clear();
                byteBuffer.put(bytes);
                byteBuffer.flip();
                socketChannel.write(byteBuffer);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
